package com.cus.tom.transfer.trans.service;


import static com.cus.tom.transfer.trans.io.DirectoryReader.LOG_TAG;

import android.app.Notification;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

import androidx.annotation.Nullable;
import androidx.documentfile.provider.DocumentFile;

import com.cus.tom.transfer.App;
import com.cus.tom.transfer.R;
import com.cus.tom.transfer.bean.Const;
import com.cus.tom.transfer.trans.io.AverageRateCounter;
import com.cus.tom.transfer.trans.io.BufferPool;
import com.cus.tom.transfer.trans.io.Channel;
import com.cus.tom.transfer.trans.io.DirectoryWriter;
import com.cus.tom.transfer.utils.Utils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;

public class ReceiveService extends TransferService {
    private String host;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if ("cancel".equals(intent.getAction())) {
            Log.d(LOG_TAG, "ReceiveService user cancelled");
            stop();
            return START_NOT_STICKY;
        }

        this.startId = startId;
        initNotification(R.string.notification_connect_new);
        host = intent.getStringExtra("host");
        String path = intent.getStringExtra("path");
        if (path == null) {
            stopSelf();
            return START_NOT_STICKY;
        }
        root = DocumentFile.fromFile(new File(path));

        acquireLocks();
        thread = new ReceiveThread();
        thread.start();

        return START_NOT_STICKY;
    }

    @Override
    public void onCreate() {
        final App app = (App) getApplicationContext();
        app.receiveStart(this);
        postUpdateButton();
    }

    @Override
    public void onDestroy() {
        showResult();
        App.getApp().receiveEnd(result);
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException();
    }

    private class ReceiveThread extends Thread {
        private final BufferPool bufferPool = new BufferPool(BufferSize);
        Socket socket = null;

        @Override
        public void interrupt() {
            {
                final Socket socket = this.socket;
                if (socket != null) {
                    try {
                        socket.setSoLinger(true, 0);
                        socket.close();
                    } catch (IOException ignored) {
                    }
                }
            }
            super.interrupt();
        }

        @Override
        public void run() {
            try {
                socket = new Socket();
                socket.setPerformancePreferences(0, 0, 1);
                socket.setTrafficClass(Const.IPTOS_THROUGHPUT);
                socket.setSendBufferSize(TcpBufferSize);
                socket.setSoTimeout(30000);
                socket.setSoLinger(true, 30);
                socket.setTcpNoDelay(false);
                socket.connect(new InetSocketAddress(InetAddress.getByName(host), Const.TCP_PORT), 4000);
                handler.post(() -> {
                    if (builder != null && notificationManager != null) {
                        builder.setContentTitle(getString(R.string.notification_receiving));
                        notificationManager.notify(startId, builder.build());
                    }
                });
                streamCopy(socket);
            } catch (SocketTimeoutException e) {
                Log.e(LOG_TAG, "socket timeout");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (IOException e) {
                Log.e(LOG_TAG, "connect failed", e);
            } finally {
                if (socket != null) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        Log.e(LOG_TAG, "socket close failed", e);
                    }
                    socket = null;
                }
                handler.post(ReceiveService.this::stop);
            }
        }

        private void streamCopy(Socket socket) throws InterruptedException, IOException {
            final int bufferSize = 112 * 1024 * 1024;
            Log.d(LOG_TAG, "receive buffer size: " + Utils.formatSize(bufferSize));
            final Channel channel = new Channel(bufferSize);
            final Progress progress = new Progress();
            final DirectoryWriter writer = new DirectoryWriter(getContentResolver(), root,
                    channel, progress, bufferPool);
            writer.start();
            Timer timer = new Timer();
            try (InputStream in = socket.getInputStream()) {
                AverageRateCounter rate = new AverageRateCounter(5);
                timer.schedule(new TimerTask() {

                    @Override
                    public void run() {
                        final Progress p = progress.get();
                        String text = p.text;
                        if (text != null) {
                            text += "\n";
                            final int max = channel.getCapacity();
                            final int used = max - channel.getAvailable();
                            text += String.format(
                                    Locale.getDefault(),
                                    getResources().getString(R.string.buffer_indicator),
                                    Utils.formatSize(used),
                                    Utils.formatSize(max));
                        } else {
                            text = getResources().getString(R.string.notification_finishing);
                        }
                        final String contentText = text;
                        final boolean indeterminate = p.max == 0;
                        final int max, now;
                        if (indeterminate) {
                            max = 0;
                            now = 0;
                        } else {
                            max = 1000;
                            now = (int) (p.now * 1000 / p.max);
                        }
                        handler.post(() -> {
                            if (builder != null && notificationManager != null) {
                                builder.setContentText(contentText)
                                        .setStyle(new Notification.BigTextStyle().bigText(contentText))
                                        .setProgress(max, now, indeterminate)
                                        .setSubText(Utils.formatSize(rate.rate()) + "/s");
                                notificationManager.notify(startId, builder.build());
                            }
                            Intent i = new Intent(Const.SEND_SPEED);
                            i.putExtra("rate", rate.rate());
                            i.putExtra("p", p);
                            sendBroadcast(i);
                        });
                    }
                }, 1000, 1000);
                while (true) {
                    ByteBuffer packet = bufferPool.pop();
                    while (packet.remaining() > 0) {
                        int read = in.read(packet.array(), packet.arrayOffset() + packet.position(), packet.remaining());
                        if (read < 0) {
                            break;
                        }
                        packet.position(packet.position() + read);
                    }
                    packet.flip();
                    if (packet.limit() < 1) {
                        bufferPool.push(packet);
                        break;
                    }
                    rate.increase(packet.limit());
                    channel.write(packet);
                }
                channel.close();
                writer.join();
                result = writer.isSuccess();
                Log.d(LOG_TAG, "receive thread finished normally");
            } finally {
                timer.cancel();
                writer.interrupt();
                Intent i = new Intent(Const.SEND_OVER);
                i.putExtra("result", result);
                i.putExtra("id", writer.getFlag());
                final Progress p = progress.get();
                i.putExtra("p", p);
                sendBroadcast(i);
            }
        }
    }
}
